home *** CD-ROM | disk | FTP | other *** search
/ Personal Computer World 2009 February / PCWFEB09.iso / Software / Resources / Browsers, Managers & Extensions / Mozilla Geode 1.5 / geode-latest.xpi / content / geode.js next >
Text File  |  2008-10-07  |  27KB  |  730 lines

  1. /* ***** BEGIN LICENSE BLOCK *****
  2.  * Version: MPL 1.1/GPL 2.0/LGPL 2.1
  3.  *
  4.  * The contents of this file are subject to the Mozilla Public License Version
  5.  * 1.1 (the "License"); you may not use this file except in compliance with
  6.  * the License. You may obtain a copy of the License at
  7.  * http://www.mozilla.org/MPL/
  8.  *
  9.  * Software distributed under the License is distributed on an "AS IS" basis,
  10.  * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
  11.  * for the specific language governing rights and limitations under the
  12.  * License.
  13.  *
  14.  * The Original Code is labs.mozilla.com code.
  15.  *
  16.  * The Initial Developer of the Original Code is Mozilla Corporation.
  17.  * Portions created by the Initial Developer are Copyright (C) 2008
  18.  * the Initial Developer. All Rights Reserved.
  19.  *
  20.  * Contributor(s):
  21.  *  Justin Dolske <dolske@mozilla.com> (original author)
  22.  *
  23.  * Alternatively, the contents of this file may be used under the terms of
  24.  * either the GNU General Public License Version 2 or later (the "GPL"), or
  25.  * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
  26.  * in which case the provisions of the GPL or the LGPL are applicable instead
  27.  * of those above. If you wish to allow use of your version of this file only
  28.  * under the terms of either the GPL or the LGPL, and not to allow others to
  29.  * use your version of this file under the terms of the MPL, indicate your
  30.  * decision by deleting the provisions above and replace them with the notice
  31.  * and other provisions required by the GPL or the LGPL. If you do not delete
  32.  * the provisions above, a recipient may use your version of this file under
  33.  * the terms of any one of the MPL, the GPL or the LGPL.
  34.  *
  35.  * ***** END LICENSE BLOCK ***** */
  36.  
  37.  
  38. Components.utils.import("resource://gre/modules/XPCOMUtils.jsm");
  39.  
  40. var GeoLocThingie = {
  41.  
  42.  
  43.     /* ---------- private memebers ---------- */
  44.  
  45.  
  46.     get _logService() {
  47.         delete this._logService;
  48.         return this._logService = Cc["@mozilla.org/consoleservice;1"].
  49.                                   getService(Ci.nsIConsoleService);
  50.     },
  51.  
  52.     get _observerService() {
  53.         delete this._observerService;
  54.         return this._observerService = Cc["@mozilla.org/observer-service;1"].
  55.                                        getService(Ci.nsIObserverService);
  56.     },
  57.  
  58.     get _contentPrefService() {
  59.         delete this._contentPrefService;
  60.         return this._contentPrefService = Cc["@mozilla.org/content-pref/service;1"].
  61.                                           getService(Ci.nsIContentPrefService);
  62.     },
  63.  
  64.     // IO service for string -> nsIURI conversion
  65.     get _ioService() {
  66.         delete this._ioService;
  67.         return this._ioService = Cc["@mozilla.org/network/io-service;1"].
  68.                                  getService(Ci.nsIIOService);
  69.     },
  70.  
  71.  
  72.     _prefBranch  : null, // Preferences service
  73.     _debug    : false,
  74.     _pagePermissions : {},
  75.  
  76.     _gps : null,
  77.  
  78.  
  79.     /*
  80.      * init
  81.      *
  82.      */
  83.     init : function () {
  84.  
  85.         // Cache references to current |this| in utility objects
  86.         this._observer._geode = this;
  87.         this._webProgressListener._geode = this;
  88.  
  89.         // Preferences.
  90.         this._prefBranch = Cc["@mozilla.org/preferences-service;1"].
  91.                            getService(Ci.nsIPrefService).getBranch("extensions.geode.");
  92.         this._prefBranch.QueryInterface(Ci.nsIPrefBranch2);
  93.         this._prefBranch.addObserver("", this._observer, false);
  94.  
  95.         // Get current preference values.
  96.         this._debug = this._prefBranch.getBoolPref("debug");
  97.  
  98.         // Attach to our location provider
  99.         this._gps = new GeolocProvider_loki();
  100.         this._gps.startup();
  101.  
  102.         // WebProgressListener for getting notification of new doc loads.
  103.         // XXX Ugh. Since we're a chrome overlay, it would be nice to just
  104.         // use gBrowser.addProgressListener(). But that isn't sending
  105.         // STATE_TRANSFERRING, and the earliest we can get at the page is
  106.         // STATE_STOP (which is onload, and is inconviently late).
  107.         // We'll use the docloader service instead, but that means we need to
  108.         // filter out loads for other windows.
  109.         var docsvc = Cc["@mozilla.org/docloaderservice;1"].
  110.                      getService(Ci.nsIWebProgress);
  111.  
  112.         var listener = this._webProgressListener;
  113.         docsvc.addProgressListener(listener, Ci.nsIWebProgress.NOTIFY_STATE_DOCUMENT);
  114.  
  115.         // Remove progress listener when the window is closed.
  116.         window.addEventListener("close", function() { docsvc.removeProgressListener(listener); }, false);
  117.  
  118.         // Open a tab for the first-run page, if needed.
  119.         if(this._prefBranch.getBoolPref("firstRun"))
  120.             this._observerService.addObserver(this._observer, "sessionstore-windows-restored", true);
  121.     },
  122.  
  123.  
  124.     /*
  125.      * log
  126.      *
  127.      * Internal function for logging debug messages to the Error Console window
  128.      */
  129.     log : function (message) {
  130.         if (!this._debug)
  131.             return;
  132.         dump("GeoLoc: " + message + "\n");
  133.         this._logService.logStringMessage("GeoLoc: " + message);
  134.     },
  135.  
  136.  
  137.     /* ---------- Utility objects ---------- */
  138.  
  139.  
  140.     /*
  141.      * _observer object
  142.      *
  143.      * Just used for watching preference changes.
  144.      */
  145.     _observer : {
  146.         _geode : null,
  147.  
  148.         QueryInterface : XPCOMUtils.generateQI([Ci.nsIObserver, 
  149.                                                 Ci.nsISupportsWeakReference]),
  150.         // nsObserver
  151.         observe : function (subject, topic, data) {
  152.  
  153.             if (topic == "nsPref:changed") {
  154.                 var prefName = data;
  155.                 this._geode.log("got change to " + prefName + " preference");
  156.  
  157.                 if (prefName == "debug") {
  158.                     this._geode._debug = 
  159.                         this._geode._prefBranch.getBoolPref("debug");
  160.                 } else if (prefName == "whatever") {
  161.                   // blah
  162.                 } else {
  163.                     this._geode.log("Oops! Pref not handled, change ignored.");
  164.                 }
  165.             } else if (topic == "sessionstore-windows-restored") {
  166.                 // Open a tab for the first-run page, if needed.
  167.                 // Ugh. The session restore code fires this notification when
  168.                 // it *starts* processing the last window. If we add the tab
  169.                 // now, it will just be overwritten by session restore! The
  170.                 // notification is fired synchronously, so we can use
  171.                 // setTimeout so we run once it finishes.
  172.                 if(this._geode._prefBranch.getBoolPref("firstRun")) {
  173.                     this._geode.log("Showing firstRun page.");
  174.                     this._geode._prefBranch.setBoolPref("firstRun", false);
  175.                     setTimeout('gBrowser.selectedTab = gBrowser.addTab("http://labs.mozilla.com/geode_welcome/");', 0);
  176.                 }
  177.             } else {
  178.                 this._geode.log("Oops! Unexpected notification: " + topic);
  179.             }
  180.         }
  181.     },
  182.  
  183.  
  184.     /*
  185.      * _webProgressListener object
  186.      *
  187.      * Internal utility object, implements nsIWebProgressListener interface.
  188.      * This is attached to the document loader service, so we get
  189.      * notifications about all page loads.
  190.      */
  191.     _webProgressListener : {
  192.         _geode : null,
  193.  
  194.         QueryInterface : XPCOMUtils.generateQI([Ci.nsIWebProgressListener,
  195.                                                 Ci.nsISupportsWeakReference]),
  196.  
  197.  
  198.         onStateChange : function (aWebProgress, aRequest,
  199.                                   aStateFlags,  aStatus) {
  200.             // STATE_START is too early, doc is still the old page.
  201.             // STATE_STOP is inconviently late (it's onload)
  202.             if (!(aStateFlags & Ci.nsIWebProgressListener.STATE_TRANSFERRING))
  203.                 return;
  204.  
  205.             var domWindow = aWebProgress.DOMWindow;
  206.             var chromeWin = domWindow
  207.                                 .QueryInterface(Ci.nsIInterfaceRequestor)
  208.                                 .getInterface(Ci.nsIWebNavigation)
  209.                                 .QueryInterface(Ci.nsIDocShellTreeItem)
  210.                                 .rootTreeItem
  211.                                 .QueryInterface(Ci.nsIInterfaceRequestor)
  212.                                 .getInterface(Ci.nsIDOMWindow)
  213.                                 .QueryInterface(Ci.nsIDOMChromeWindow);
  214.             if (chromeWin != window)
  215.                 return;
  216.  
  217.             this._geode.log("onStateChange accepted: req = " +
  218.                             (aRequest ?  aRequest.name : "(null)") +
  219.                             ", flags = 0x" + aStateFlags.toString(16));
  220.  
  221.             this._geode._injectNavigatorGeolocator(domWindow);
  222.         },
  223.  
  224.         // stubs for the nsIWebProgressListener interfaces which we don't use.
  225.         onProgressChange : function() { },
  226.         onLocationChange : function() { },
  227.         onStatusChange   : function() { },
  228.         onSecurityChange : function() { },
  229.     },
  230.  
  231.  
  232.     /*
  233.      * getShortHostname
  234.      */
  235.     _getShortHostname : function (aDOMLocation) {
  236.         var shortHostname = aDOMLocation.host;
  237.         if (!shortHostname)
  238.             shortHostname = aDOMLocation; // for file:///blah.html
  239.         return shortHostname;
  240.     },
  241.  
  242.  
  243.     /*
  244.      * _eventListener
  245.      */
  246.     _eventListener : function (aEvent) {
  247.         this.log("_eventListener got " + aEvent.type + " event for " + aEvent.target.location);
  248.  
  249.         // XXX I think the scoping is right here, but might be worth checking
  250.         //     for any funkyness when the request comes from an iframe or such.
  251.         var win = aEvent.target;
  252.         if (!(win instanceof Ci.nsIDOMWindow)) {
  253.             this.log("Error: event target wasn't a window.")
  254.             return;
  255.         }
  256.  
  257.         // If the user already granted approval, don't ask again.
  258.         var [havePerms, allowed, fuzzLevel] = this._getPagePermissions(win.location);
  259.         if (havePerms) {
  260.             if (allowed) {
  261.                 this.log("Page allowed access.");
  262.                 this._sendPositionUpdate(win, fuzzLevel)
  263.                 return;
  264.             } else {
  265.                 this.log("Page not allowed access.");
  266.                 return;
  267.             }
  268.         }
  269.  
  270.         this.log("Requesting user permission to reveal location");
  271.  
  272.         var promptName = "geode-request";
  273.  
  274.         var shortHostname = this._getShortHostname(win.location);
  275.         var promptText = "The page at " + shortHostname +
  276.                          " wants to know where you are. Tell them:";
  277.  
  278.  
  279.         var geode = this; // just for the closure
  280.         var buttons = [
  281.             {
  282.                 label:     "Exact location",
  283.                 accessKey: "l",
  284.                 popup:     null,
  285.                 callback:  function(bar) {
  286.                                 fuzzLevel = 0;
  287.                                 geode._setPagePermission(win.location, fuzzLevel, checkbox.checked);
  288.                                 geode._sendPositionUpdate(win, fuzzLevel);
  289.                            }
  290.             },
  291.  
  292.             {
  293.                 label:     "Neighborhood",
  294.                 accessKey: "h",
  295.                 popup:     null,
  296.                 callback:  function() {
  297.                                 fuzzLevel = 200; // 200m radius
  298.                                 geode._setPagePermission(win.location, fuzzLevel, checkbox.checked);
  299.                                 geode._sendPositionUpdate(win, fuzzLevel);
  300.                            }
  301.             },
  302.  
  303.             {
  304.                 label:     "City",
  305.                 accessKey: "C",
  306.                 popup:     null,
  307.                 callback:  function() {
  308.                                 fuzzLevel = 10000; //10km radius
  309.                                 geode._setPagePermission(win.location, fuzzLevel, checkbox.checked);
  310.                                 geode._sendPositionUpdate(win, fuzzLevel);
  311.                            }
  312.             },
  313.  
  314.             {
  315.                 label:     "Nothing",
  316.                 accessKey: "N",
  317.                 popup:     null,
  318.                 callback:  function() {
  319.                                 fuzzLevel = -1;
  320.                                 geode._setPagePermission(win.location, fuzzLevel, checkbox.checked);
  321.                                 geode.log("User denied sending location.");
  322.                             }
  323.             },
  324.         ];
  325.  
  326.  
  327.         var notifyBox = this._getNotificationBox(win);
  328.  
  329.         var oldBar = notifyBox.getNotificationWithValue(promptName);
  330.  
  331.         var newBar = notifyBox.appendNotification(
  332.                                 promptText, promptName, null,
  333.                                 notifyBox.PRIORITY_INFO_MEDIUM, buttons);
  334.  
  335.         var checkbox = document.createElementNS(
  336.                         "http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul",
  337.                         "checkbox");
  338.         checkbox.setAttribute("id", "rememberChoice");
  339.         checkbox.setAttribute("label", "Always do this without asking");
  340.         newBar.appendChild(checkbox);
  341.  
  342.         if (oldBar) {
  343.             this.log("(...removing preexisting notification bar)");
  344.             notifyBox.removeNotification(oldBar);
  345.         }
  346.     },
  347.  
  348.  
  349.  
  350.  
  351.     /* ------- Internal methods / callbacks for document integration ------- */
  352.  
  353.  
  354.  
  355.  
  356.     _scriptToInject : null,
  357.  
  358.     /*
  359.      * _injectNavigatorGeolocator
  360.      *
  361.      * Injects window.navigator.geolocation into the specified DOM window.
  362.      */
  363.     _injectNavigatorGeolocator: function (aWindow) {
  364.         this.log("Injecting to " + aWindow.location);
  365.  
  366.         if (!this._scriptToInject) {
  367.             this.log("Reading code for injection...");
  368.             this._scriptToInject = this._readFile();
  369.         }
  370.  
  371.         var sandbox = new Components.utils.Sandbox(aWindow);
  372.         sandbox.__proto__ = aWindow.wrappedJSObject
  373.  
  374.         Components.utils.evalInSandbox(this._scriptToInject, sandbox);
  375.  
  376.         aWindow.addEventListener("x-geode-locationRequest-getPosition",       function (e) { GeoLocThingie._eventListener(e); }, false, true);
  377.         aWindow.addEventListener("x-geode-locationRequest-watchPosition",     function (e) { GeoLocThingie._eventListener(e); }, false, true);
  378.         aWindow.addEventListener("x-geode-locationRequest-stopWatchPosition", function (e) { GeoLocThingie._eventListener(e); }, false, true);
  379.     },
  380.  
  381.  
  382.     /*
  383.      * _readFile
  384.      *
  385.      * Read a file from the extension's directory, and return it as a string.
  386.      */
  387.  
  388.     _readFile : function () {
  389.  
  390.         // Get at the directory where the extension is installed.
  391.         var MY_ID = "geode@labs.mozilla.com";
  392.         var em = Cc["@mozilla.org/extensions/manager;1"].
  393.                  getService(Ci.nsIExtensionManager);
  394.         var file = em.getInstallLocation(MY_ID).getItemFile(MY_ID, "install.rdf").parent;
  395.  
  396.         // Get at $EXTDIR/content/injected.js
  397.         file.append("content");
  398.         file.append("injected.js");
  399.         if (!file.exists()) {
  400.             this.log("ERROR: $EXTDIR/content/injected.js doesn't exist!");
  401.             return;
  402.         }
  403.  
  404.         // Slurp the contents of the file into a string.
  405.         var line = { value: "" };
  406.         var inputStream, lineStream, hasMore;
  407.  
  408.         inputStream = Cc["@mozilla.org/network/file-input-stream;1"].
  409.                       createInstance(Ci.nsIFileInputStream);
  410.         inputStream.init(file, 0x01, -1, null); // RD_ONLY
  411.         lineStream = inputStream.QueryInterface(Ci.nsILineInputStream);
  412.  
  413.         var bigString = "", hasMore;
  414.         do {
  415.             hasMore = lineStream.readLine(line);
  416.             bigString += line.value;
  417.             bigString += "\n";
  418.         } while (hasMore);
  419.         lineStream.close();
  420.  
  421.         return bigString;
  422.     },
  423.  
  424.  
  425.     /*
  426.      * _sendPositionUpdate
  427.      */
  428.     _sendPositionUpdate : function (aWindow, aFuzzLevel) {
  429.  
  430.         var geode = this;
  431.  
  432.         // Fuzz the exact location we're given, so it's imprecise to the level
  433.         // specified by the user. This is a little tricky, because the
  434.         // location is in degrees of lat/long, and the fuzz level is in
  435.         // meters. Converting latitude (N/S) to meters is basically linear,
  436.         // but converting longtitude (E/W) to meters depends on your latitude
  437.         // (eg, 1 degree of long. @ the equator is much larger than 1 degree
  438.         // near the poles).
  439.         function fuzzLocation(pos, fuzzLevel) {
  440.             geode.log("Fuzzing location...");
  441.             // The following is based on http://en.wikipedia.org/wiki/Latitude#Degree_length
  442.             const m1 = 111132.92;
  443.             const m2 = -559.82;
  444.             const m3 = 1.175;
  445.             const m4 = -0.0023;
  446.             const p1 = 111412.84;
  447.             const p2 = -93.5;
  448.             const p3 = 0.118;
  449.  
  450.             var latRads = pos.latitude * (2.0 * Math.PI / 360.0);
  451.  
  452.             // Calculates the degrees-per-meter at a specific lat/long.
  453.             var latDegPerMeter = 1 / (m1 +
  454.                                       (m2 * Math.cos(2 * latRads)) +
  455.                                       (m3 * Math.cos(4 * latRads)) +
  456.                                       (m4 * Math.cos(6 * latRads)));
  457.             var longDegPerMeter = 1 / ((p1 * Math.cos(1 * latRads)) +
  458.                                        (p2 * Math.cos(3 * latRads)) +
  459.                                        (p3 * Math.cos(5 * latRads)));
  460.  
  461.             var latFuzz = latDegPerMeter * fuzzLevel;
  462.             var longFuzz = longDegPerMeter * fuzzLevel;
  463.             geode.log("Fuzzlevel " + fuzzLevel + " --> " + longFuzz + " x " + latFuzz);
  464.  
  465.             // If we're fuzzing, set the accuracy.
  466.             // XXX seems like the right thing to do, but maybe we shouldn't
  467.             //     let the site know we're fuzzing?
  468.             if (fuzzLevel && fuzzLevel > pos.accuracy)
  469.                 pos.accuracy = fuzzLevel;
  470.  
  471.             // Scale by fuzz by a randon number between -1 and 1.
  472.             pos.latitude += (2 * Math.random() - 1) * latFuzz;
  473.             pos.longtitude += (2 * Math.random() - 1) * longFuzz;
  474.  
  475.             // Overflow clamping / wrapping.
  476.             if (pos.latitude < -90)
  477.                 pos.latitude = -90;
  478.             else if (pos.latitude > 90)
  479.                 pos.latitude = 90;
  480.  
  481.             if (pos.longitude < -180)
  482.                 pos.longitude += 360;
  483.             else if (pos.longitude > 180)
  484.                 pos.longitude -= 360;
  485.         }
  486.  
  487.  
  488.         function reallySendPositionUpdate(message) {
  489.             geode.log("sending position update to " + aWindow.location);
  490.  
  491.             var sandbox = new Components.utils.Sandbox(aWindow);
  492.             sandbox.__proto__ = aWindow.wrappedJSObject
  493.  
  494.             scriptToRun = "window.navigator.geolocation._notifyListeners(" +
  495.                           message.toSource() + ");";
  496.  
  497.             Components.utils.evalInSandbox(scriptToRun, sandbox);
  498.         }
  499.  
  500.         function successHandler(position) {
  501.             geode.log("Loki provider got position");
  502.             // nsIDOMGeoPosition
  503.             var loc = {
  504.                 latitude  : position.latitude,
  505.                 longitude : position.longitude,
  506.                 altitude  : null,
  507.                 accuracy  : null,
  508.                 altitudeAccuracy  : null,
  509.                 heading   : null,
  510.                 velocity  : null,
  511.                 timestamp : Date.now(),
  512.             };
  513.  
  514.             fuzzLocation(loc, aFuzzLevel);
  515.             reallySendPositionUpdate(loc);
  516.         }
  517.  
  518.         function failureHandler(error) {
  519.             var errorString = "Loki provider failed: ";
  520.  
  521.             switch (error) {
  522.               case 1:
  523.                 errorString += "Scanner not found."; break;
  524.               case 2:
  525.                 errorString += "WiFi not available."; break;
  526.               case 3:
  527.                 errorString += "No WiFi in range."; break;
  528.               case 4:
  529.                 errorString += "Unauthorized."; break;
  530.               case 5:
  531.                 errorString += "Invalid Application Key."; break;
  532.               case 6:
  533.                 errorString += "Location cannot be determined."; break;
  534.               case 7:
  535.                 errorString += "Proxy unauthorized."; break;
  536.               default:
  537.                 errorString += "Unknown error."; break;
  538.             }
  539.  
  540.             geode.log("Loki provider failed: " + error +
  541.                        " (" + errorString + ")");
  542.  
  543.             // nsIDOMGeoPositionError
  544.             var err = {
  545.                 code : error,
  546.                 message : errorString
  547.             };
  548.             reallySendPositionUpdate(err);
  549.         }
  550.  
  551.         // XXX The actual nsIGeolocationProvider API is awkward to use here,
  552.         // because we jsut want to fetch the position asynchronously. For now
  553.         // we'll just fudge our way with an async API.
  554.         // var loc = this._gps.currentPosition;
  555.         this._gps.getLocationAsync(successHandler, failureHandler);
  556.     },
  557.  
  558.  
  559.     /*
  560.      * _getPagePermissions
  561.      *
  562.      * Checks to see if a page should be allowed access to the current
  563.      * location, without user interaction.
  564.      *
  565.      * Returns [havePerms, allowed, fuzzLevel]
  566.      *
  567.      * havePerms -- true if there's an existing permissions decision.
  568.      * allowed -- true if the existing decision was to allow the page access
  569.      *            to the location, false is the decision was to deny access.
  570.      * fuzzLevel -- fuzzlevel for page's allowed access
  571.      */
  572.     _getPagePermissions : function (aDOMLocation) {
  573.         var shortName = this._getShortHostname(aDOMLocation);
  574.  
  575.         this.log("Checking page permissions for " + shortName);
  576.  
  577.         var uri = this._ioService.newURI(aDOMLocation, null, null);
  578.         var fuzzLevel = this._contentPrefService.getPref(uri, "labs.geode.fuzzLevel");
  579.         if (typeof fuzzLevel != "undefined") {
  580.             this.log("Found permanent pref, fuzz is " + fuzzLevel);
  581.             return [true, (fuzzLevel >= 0), fuzzLevel];
  582.         }
  583.  
  584.         var perms = this._pagePermissions[shortName];
  585.         if (!perms)
  586.             return [false, undefined, undefined];
  587.  
  588.         var timeout = perms.lastUsed;
  589.         // Keep permission grants/denials alive for a short period of time,
  590.         // to help avoid the prompts from being annoying when navigating
  591.         // location-aware sites. Maybe we should just get rid of this if
  592.         // we can save the choice permanently?
  593.         if (perms.allowed)
  594.             timeout += 1 * 60 * 1000; // 1 minute
  595.         else
  596.             timeout += 10 * 1000; // 10 seconds
  597.  
  598.         var now = Date.now();
  599.         if (now > timeout) {
  600.             this.log("Page previously " + (perms.allowed ? "allowed" : "denied") +
  601.                      " permission, but has timed out");
  602.             delete this._pagePermissions[shortName];
  603.             return [false, undefined, undefined];
  604.         }
  605.  
  606.         perms.lastUsed = now;
  607.  
  608.         this.log("Page is " + (perms.allowed ? "allowed" : "denied") +
  609.                  " with fuzz level " + perms.fuzzLevel);
  610.  
  611.         return [true, perms.allowed, perms.fuzzLevel];
  612.     },
  613.  
  614.  
  615.     /*
  616.      * _setPagePermission
  617.      *
  618.      * Remembers a user's choice for allowing a page to automatically get the
  619.      * position. The choice is always remembered for a short period of time,
  620.      * and can optionally be saved permanently.
  621.      *
  622.      * aFuzzLevel can be -1 to indicate permission was denied.
  623.      */
  624.     _setPagePermission : function (aDOMLocation, aFuzzLevel, isPermanent) {
  625.         var shortName = this._getShortHostname(aDOMLocation);
  626.  
  627.         this.log("setting permissions for " + shortName + " to " +
  628.                     (aFuzzLevel >= 0 ? ("fuzz level " + aFuzzLevel) : "denied"));
  629.         if (isPermanent) {
  630.             this.log("saving permanently...");
  631.             var uri = this._ioService.newURI(aDOMLocation, null, null);
  632.             this._contentPrefService.setPref(uri, "labs.geode.fuzzLevel", aFuzzLevel);
  633.         } else {
  634.             this._pagePermissions[shortName] = {
  635.                                              allowed:   (aFuzzLevel >= 0),
  636.                                              fuzzLevel: aFuzzLevel,
  637.                                              lastUsed: Date.now()
  638.                                            };
  639.         }
  640.     },
  641.  
  642.  
  643.     /*
  644.      * _getNotificationBox
  645.      */
  646.     _getNotificationBox : function (aWindow) {
  647.         try {
  648.             // Get topmost window, in case we're in a frame.
  649.             var notifyWindow = aWindow.top
  650.  
  651.             // Find the <browser> which contains notifyWindow, by looking
  652.             // through all the open windows and all the <browsers> in each.
  653.             var wm = Cc["@mozilla.org/appshell/window-mediator;1"].
  654.                      getService(Ci.nsIWindowMediator);
  655.             var enumerator = wm.getEnumerator("navigator:browser");
  656.             var tabbrowser = null;
  657.             var foundBrowser = null;
  658.         
  659.             while (!foundBrowser && enumerator.hasMoreElements()) {
  660.                 var win = enumerator.getNext();
  661.                 tabbrowser = win.getBrowser();
  662.                 foundBrowser = tabbrowser.getBrowserForDocument(
  663.                                                   notifyWindow.document);
  664.             }
  665.  
  666.             // Return the notificationBox associated with the browser.
  667.             if (foundBrowser)
  668.                 return tabbrowser.getNotificationBox(foundBrowser)
  669.         } catch (e) {
  670.             this.log("No notification box available!");
  671.             return null;
  672.         }
  673.     },
  674.  
  675. };
  676.  
  677.  
  678.  
  679.  
  680. window.addEventListener("load", function() {GeoLocThingie.init();}, false);
  681.  
  682.  
  683.  
  684.  
  685.  
  686. function GeolocProvider_loki() { };
  687.  
  688. GeolocProvider_loki.prototype = {
  689.  
  690.     _loki : null,
  691.  
  692.     _lokiFailure : function (error) {
  693.         dump("LOKI FAILURE: Code " + error + "\n");
  694.     },
  695.  
  696.  
  697.     startup : function () {
  698.         this._loki = Cc["@skyhookwireless.com/locationService;1"].
  699.                      createInstance(Ci.wpsILocationService);
  700.         this._loki.onFailure = this._lokiFailure;
  701.         this._loki.setKey("mozilla.org");
  702.         return;
  703.     },
  704.  
  705.     isReady : function () {
  706.         return true;
  707.     },
  708.  
  709.     watch : function (callback) {
  710.         // Loki doesn't seem to have a "tell me when the position changes" API.
  711.         throw "not implemented";
  712.     },
  713.  
  714.     get currentLocation() {
  715.         throw "Loki really wants an async API for this."
  716.     },
  717.  
  718.     // XXX this isn't in nsIGeolocationProvider
  719.     getLocationAsync : function (successCallback, failureCallback) {
  720.         this._loki.onSuccess = successCallback;
  721.         this._loki.onFailure = failureCallback;
  722.         this._loki.requestLocation(true, this._loki.LIMITED_STREET_ADDRESS_LOOKUP);
  723.     },
  724.   
  725.     shutdown : function () {
  726.         this._loki = null;
  727.         return;
  728.     }
  729. }; // end of GeolocProvider_loki implementation
  730.